openvpn 配置

十月 26, 2025 更新: 九月 11, 2026 [网络与安全] #openvpn #networkmanager

安装和配置

apt install openvpn
# 覆盖默认配置, 探测192.168.2.1可达
sudo tee /etc/systemd/system/openvpn-client@work.service <<EOF
[Unit]
Description=OpenVPN tunnel for %I after IP 192.168.2.1 to be reachable
After=network-online.target
Wants=network-online.target
[Service]
Type=notify
PrivateTmp=true
WorkingDirectory=/etc/openvpn/client
ExecStartPre=/bin/bash -c 'until ping -c 1 -W 5 192.168.2.1; do sleep 1; done
ExecStart=/usr/sbin/openvpn --suppress-timestamps --nobind --config %i.conf
CapabilityBoundingSet=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SETPCAP CAP_SYS_CHROOT CAP_DAC_OVERRIDE
LimitNPROC=10
DeviceAllow=/dev/null rw
DeviceAllow=/dev/net/tun rw
ProtectSystem=true
ProtectHome=true
KillMode=process
[Install]
WantedBy=multi-user.target
EOF

cat <<EOF | sudo tee /etc/openvpn/client/work.conf
client
dev tun
proto tcp
remote <ip> <port>
resolv-retry infinite
nobind
persist-key
persist-tun
comp-lzo no

auth-user-pass
# openvpn3 客户端支持账户认证, openvpn2 好像支持环境变量热证
# <auth-user-pass>
# VPN_USERNAME_HERE
# VPN_PASSWORD_HERE
# </auth-user-pass>
remote-cert-tls server

data-ciphers AES-256-GCM:AES-256-CBC
data-ciphers-fallback AES-256-CBC
verb 3
<ca>
-----BEGIN CERTIFICATE-----
XXX base 64
-----END CERTIFICATE-----
</ca>
EOF
sudo systemctl start openvpn-client@work.service
# 可以手动调节metric 防止影响局域网路由
sudo ip route change default via 10.8.0.1 dev tun0 metric 300

服务端配置

可以参考ldap账户认证

配置路由

ip route
# 192.168.5.0/24 dev eno1 proto kernel scope link src 192.168.5.23 metric 100 
# 192.168.4.0/22 via 10.8.0.9 dev tun0 
# 192.168.4.0/22 via 10.8.0.9 dev tun0 metric 10
ip route get 192.168.5.244
# 192.168.5.244 dev eno1 src 192.168.5.23 uid 1000
#     cache

# 为什么经过dev tun0 的路由地址metric优先级(10)更高, 但是路由却走了直连的路由???

原因

如果你真的想让 192.168.5.244 走 tun0 怎么办?

这是个非常规需求(因为 192.168.5.244 和你本机在同一个局域网),但技术上可以做到:

添加更具体的 /32 路由(推荐)

sudo ip route add 192.168.5.244 via 10.8.0.9 dev tun0 metric 5

使用策略路由(高级)

通过 ip rule + 自定义路由表,对特定用户/进程/防火墙标记的流量使用不同路由。

sudo ip rule add to 192.168.5.244/24 lookup 100
sudo ip route add default via 10.8.0.9 dev tun0 table 100

报错 Nexthop has invalid gateway:网关必须写 tun peer(2026-09 实测)

给隧道内某个未推送网段的主机加 /32 路由时,把网关写成了服务端内网网关:

sudo ip route add 192.168.1.1/32 via 192.168.4.1 dev tun0
# Error: Nexthop has invalid gateway.

内核要求 nexthop 在指定设备的直连网络内(on-link)。tun0 是点对点口(10.8.0.6 peer 10.8.0.5/32),直连的只有 peer 一个地址;192.168.4.1 只是经路由(192.168.4.0/22 via 10.8.0.5)才可达的远端地址,不能直接当 nexthop。隧道内所有目的地址都由 peer 代投,网关一律写 peer(即已推送路由里的 via 地址):

sudo ip route add 192.168.1.1/32 via 10.8.0.5 dev tun0

也可以加 onlink 强制跳过检查,但语义绕,不推荐。

顺带实测:网关写自己的 tun 地址(via 10.8.0.6)内核同样接受(自己的地址必然 on-link),且路由会被直接退化/简化为直连——ip route get 里 via 消失,变成 192.168.1.1 dev tun0,报文路径与写 peer 完全一致。能用,但不如写 peer 语义清晰(真实下一跳、与推送路由风格一致)。

NetworkManager/GNOME 为什么启不动 VPN(2026-09 实测)

现象

用 GNOME 设置导入 .ovpn(NetworkManager openvpn 插件)建好连接后,点「启用」不报错,但退出再进页面仍是未启用状态;ping 不通 VPN 虚拟内网地址(隧道根本没起来)。命令行激活同样失败:

$ nmcli con up smyl
错误:连接激活失败:Could not find source connection.

服务端其实没问题

用 openvpn CLI 带同一份 CA 凭据直接握手(--dev null 只测控制面、不动路由):

VERIFY OK: depth=0, CN=smyl
TLS: move_session: dest=TM_ACTIVE ...
PUSH: Received control message: 'PUSH_REPLY,route 192.168.4.0 255.255.252.0,ifconfig 10.8.0.6 10.8.0.5,...'

服务器、证书、账号全正常,问题只在客户端激活环节。

根因:NM 的 VPN 必须叠加在「主连接」上

NetworkManager 日志(sudo journalctl -u NetworkManager):

audit: op="connection-activate" name="smyl" ... result="fail" reason="Could not find source connection."

NM 源码(src/core/nm-manager.c 的 _new_active_connection()):激活 VPN 时必须先有 parent——未指定 specific base connection 时取 priv->primary_connection(即 NM 托管的、持有默认路由的已激活连接),拿不到就直接报 "Could not find source connection."。也就是说 NM 把 VPN 建模为「基连接之上的叠加层」,不允许凭空激活。

本机主网络是 ifupdown 静态配置的桥(vmbr0/eno1,/etc/network/interfaces),而 NM 默认 [ifupdown] managed=false → 该网络对 NM 是「未托管」(nmcli dev status 里 vmbr0 = 未托管,NM 整体状态只到 CONNECTED_LOCAL)→ NM 眼里没有任何带默认路由的连接 → primary_connection 为空 → 任何 VPN 启用必失败。

结论:只要主网络不由 NM 托管(ifupdown/systemd-networkd 的静态桥、libvirt 桥接等常见于服务器/虚拟化主机),GNOME/NM 的 VPN 开关就用不了——不是配置问题,是 NM 的模型限制。这类机器要么把主网络重构给 NM 管理(动网有风险),要么绕开 NM 直接用 openvpn CLI / systemd 服务。

普通用户为什么跑不了 openvpn(systemctl --user 也不行)

即使 /dev/net/tun 是 0666,普通用户运行 openvpn 仍失败:

ERROR: Cannot ioctl TUNSETIFF tun-probe2: Operation not permitted (errno=1)

TUNSETIFF(创建 tun 口)要求 CAP_NET_ADMIN,与设备文件权限无关;而 systemd user manager 运行在普通用户权限域内,无法授予该 capability,所以把 unit 复制成 ~/.config/systemd/user/ 服务同样失败。openvpn 需要的其实只是「建 tun + 配 IP + 写路由」那一下 root,TLS/认证本身不需要特权。

解法

方案做法说明
systemd 系统服务(推荐)配置放 /etc/openvpn/client/*.conf,sudo systemctl enable --now openvpn-client@xxx不依赖 NM,开机自启,无脑可用
普通用户免密启停sudoers 窄白名单放行该 unit 的 systemctl 命令比 systemctl --user 简单直接

sudoers 白名单示例:

# /etc/sudoers.d/openvpn-smyl
peter ALL=(root) NOPASSWD: /usr/bin/systemctl start openvpn-client@smyl, /usr/bin/systemctl stop openvpn-client@smyl, /usr/bin/systemctl restart openvpn-client@smyl, /usr/bin/systemctl status openvpn-client@smyl

之后普通用户 sudo systemctl start openvpn-client@smyl 即可随时启停,无需输密码。

systemd 客户端连不上的坑(2026-09 实测)

remote 不是 NM 的 host:port 写法

把 GNOME/NM 连接里的 remote(61.149.15.167:7896,冒号)原样抄进 /etc/openvpn/client/*.conf 后,服务反复重启,日志:

RESOLVE: Cannot resolve host address: 61.149.15.167:7896:1194 (Name or service not known)

openvpn 的 remote 是空格分隔 <host> <port>;冒号写法会把整串当主机名去解析(结尾多出的 :1194 是默认端口)。NM 的 vpn.data 里是 host:port 单字段,两处语法不同,别混抄。

小心 CRLF 行尾

配置文件被保存成 CRLF(\r\n)后,肉眼、grep -n 都看不出差异,但 sed 的 $ 锚点、diff 等工具会静默失效。统一转 LF 再改:

sudo sed -i 's/\r$//' /etc/openvpn/client/smyl.conf